from datetime import datetime
import logging

import aiomysql

logging.basicConfig(level=logging.INFO)


async def create_pool():
    logging.info('create database connection pool')
    global __pool
    __pool = await aiomysql.create_pool(
        host='localhost',
        port=3306,
        user='root',
        password='root',
        db='test',
        autocommit=True
    )


async def select(sql, args, size=None):
    print(sql, args)
    global __pool
    with (await __pool) as conn:
        cur = await conn.cursor(aiomysql.DictCursor)
        await cur.execute(sql.replace('?', '%s'), args or ())
        if size:
            rs = await cur.fetchmany(size)
        else:
            rs = await cur.fetchall()
        await cur.close()
        logging.info('rows returned: %s' % len(rs))
        return rs


async def execute(sql, args):
    print(sql, args)
    with(await __pool) as conn:
        try:
            cur = await conn.cursor()
            await cur.execute(sql.replace('?', '%s'), args)
            affected = cur.rowcount
            await cur.close()
        except BaseException as e:
            raise
        return affected


class Field:
    def __init__(self, name, column_type, primary_key, default):
        self.name = name
        self.column_type = column_type
        self.primary_key = primary_key
        self.default = default

    def __str__(self) -> str:
        return '<%s, %s:%s>' % (self.__class__.__name__, self.column_type, self.name)


class IntField(Field):

    def __init__(self, name=None, primary_key=True, default=0):
        super().__init__(name, 'bigint', primary_key, default)


class StringField(Field):
    def __init__(self, name=None, primary_key=False, default=None, ddl='varchar(100)'):
        super().__init__(name, ddl, primary_key, default)


class DateTimeField(Field):

    def __init__(self, name=None, primary_key=False, default=datetime.now(), ddl='datetime'):
        super().__init__(name, ddl, primary_key, default)


class BooleanField(Field):

    def __init__(self, name=None, column_type='boolean', primary_key=False, default=False):
        super().__init__(name, column_type, primary_key, default)


class FloatField(Field):

    def __init__(self, name=None, column_type=None, primary_key=False, default=0.0):
        super().__init__(name, column_type, primary_key, default)


class TextField(Field):

    def __init__(self, name=None, default=None):
        super().__init__(name, 'text', False, default)


class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        if name == 'Model':
            return type.__new__(cls, name, bases, attrs)
        tableName = attrs.get('__table__', None) or name
        mappings = dict()
        fields = []
        primaryKey = None
        for k, v in attrs.items():
            if isinstance(v, Field):
                mappings[k] = v
                if v.primary_key:
                    if primaryKey:
                        raise RuntimeError('Duplicate primary key for filed: %s' % k)
                    primaryKey = k
                else:
                    fields.append(k)
        if not primaryKey:
            raise RuntimeError('primary key not found.')
        for k in mappings.keys():
            attrs.pop(k)
        escaped_fields = list(map(lambda f: '`%s`' % f, fields))
        attrs['__mappings__'] = mappings
        attrs['__table__'] = tableName
        attrs['__primary_key__'] = primaryKey
        attrs['__fields__'] = fields
        attrs['__select__'] = 'select `%s`, %s from `%s`' % (primaryKey, ','.join(escaped_fields), tableName)
        attrs['__insert__'] = 'insert into `%s` (%s) values (%s)' % (
            tableName, ','.join(escaped_fields), ','.join(['%s' for i in range(len(escaped_fields))]))
        attrs['__update__'] = 'update `%s` set %s where `%s` = ?' % (tableName, '%s', primaryKey)
        attrs['__delete__'] = 'delete from `%s` where `%s` = ?' % (tableName, primaryKey)
        return type.__new__(cls, name, bases, attrs)


class Model(dict, metaclass=ModelMetaclass):

    # def __init__(self, **kwargs) -> None:
    #     super().__init__(**kwargs)

    def __getattr__(self, item):
        return self[item]

    def getvalordefault(self, key):
        val = self.get(key)
        print(' key is %s , val is %s' % (key, val))
        if not val:
            val = self.__mappings__.get(key).default
        return val

    @classmethod
    async def findAll(cls, where=None, args=None, **kw):
        ' find objects by where clause. '
        sql = [cls.__select__]
        if where:
            sql.append('where')
            sql.append(where)
        if args is None:
            args = []
        orderBy = kw.get('orderBy', None)
        if orderBy:
            sql.append('order by')
            sql.append(orderBy)
        limit = kw.get('limit', None)
        if limit is not None:
            sql.append('limit')
            if isinstance(limit, int):
                sql.append('?')
                args.append(limit)
            elif isinstance(limit, tuple) and len(limit) == 2:
                sql.append('?, ?')
                args.extend(limit)
            else:
                raise ValueError('Invalid limit value: %s' % str(limit))
        rs = await select(' '.join(sql), args)
        return [cls(**r) for r in rs]

    @classmethod
    async def find(cls, key):
        rs = await select('%s where %s = ?' % (cls.__select__, cls.__primary_key__), [key], 1)
        if len(rs) > 0:
            return cls(**rs[0])
        else:
            return None

    @classmethod
    async def findByEmail(cls, email):
        rs = await select('select * from %s where email = ?' % cls.__table__, [email], 1)
        if len(rs) > 0:
            return cls(**rs[0])
        else:
            return None

    @classmethod
    async def remove(cls, key):
        return await execute(cls.__delete__, [key])

    async def save(self):
        return await execute(self.__insert__, list(map(lambda k: self.getvalordefault(k), self.__fields__)))

    async def update(self):
        """ update user set %s where id = ? """
        update_field = []
        update_val = []
        if self.__primary_key__ not in self.keys():
            raise ValueError('update should contain primary key')
        if len(self.keys()) < 2:
            raise RuntimeError(' update args must more than 2')
        for k in self.keys():
            if k != self.__primary_key__:
                update_field.append('%s = ?' % k)
                update_val.append(self.get(k))
        update_val.append(self.get(self.__primary_key__))
        return await execute(self.__update__ % ','.join(update_field), update_val)

    @classmethod
    async def remove(cls, key):
        return await execute(cls.__delete__ % (cls.__table__, key))

    @classmethod
    async def findNumber(cls, selectField, where=None, args=None):
        ' find number by select and where. '
        sql = ['select %s _num_ from `%s`' % (selectField, cls.__table__)]
        if where:
            sql.append('where')
            sql.append(where)
        rs = await select(' '.join(sql), args, 1)
        if len(rs) == 0:
            return None
        return rs[0]['_num_']

# async def find_user():
#     await create_pool()
#     return await User.find(3)
#
#
# async def delete_user():
#     await create_pool()
#     return await User.remove(7)
#
#
# async def save_user():
#     await create_pool()
#     return await User(nick_name='laowang', account='110@qq.com', password='999', age=30).save()
#
#
# async def update_user():
#     await create_pool()
#     return await User(id=8, nick_name='liwu', account='1234', password='1234', age=22).update()
